#!/usr/bin/env python
# coding: utf-8

# # 鸢尾花数据分类示例
# ## 步骤1：准备数据

# ### 方式1：加载scikit-learn自带数据集

# In[1]:


from sklearn.datasets import load_iris
iris_data = load_iris()    #默认返回Bunch类型的对象
print("load_iris(返回的数据类型)：", type(iris_data))
# 'sklearn.utils.Bunch'类似于字典，可以通过key来访问对应的值。
print("keys：",iris_data.keys())
# 键data对应的对象是描述特征的数组，一共150个元素，这里取前3个元素。
print("前3行数据：\n",iris_data["data"][:3],sep="")

# 通过feature_names，可以查看各特征依次表示的含义。
print(iris_data["feature_names"])

# 键target对应的对象是目标值（类别），一共150个，依次与data中的每个元素对应。
print("前3行的分类标记：", iris_data["target"][:3])

# 通过target_names，可以查看各目标值依次对应的含义。
print(iris_data["target_names"])

# 如果导入的时候设置as_frame=True，
# 那么data和frame两个键对应的都是特征数据构成的DataFrame对象，
# data对应的数据不包含target列，frame对应的数据包含target列
# 键target对应的是目标值构成的Series对象。
iris_data = load_iris(as_frame=True)
print(type(iris_data))
print(iris_data.keys())
#显示data、frame和target的前3行
print("data关键字对应的数据前3行：\n",iris_data["data"][:3],sep="")
print("frame关键字对应的数据前3行：\n",iris_data["frame"][:3],sep="")
print("target关键字对应的数据前3行：\n",iris_data["target"][:3],sep="")

# 如果导入数据时使用参数return_X_y=True，
# 则返回特征数组和目标数组构成的元组，可以分别赋给两个变量。
# 这种方式下无法查看各特征的名称和目标数值表示的分类名称。
X, y = load_iris(return_X_y=True)
print(X[:3])
print(y[:3])


# ### 方式2：先下载，然后加载iris.data数据文件

# In[2]:


# 导入iris.data中鸢尾花数据集，并对数据进行转换
import pandas as pd
from sklearn.utils import shuffle

# 通过pd.set_option设置DataFrame打印时右对齐
pd.set_option("display.unicode.east_asian_width",True)

#pd.set_option("display.width",500)  #设置较大的宽度，不换行
#pd.set_option("display.max_columns",4)  #最大显示列数
#pd.set_option("display.max_columns",None)  #显示所有列
#pd.set_option("max_colwidth",20)  #每列内容的显示宽度

iris_data = pd.read_csv("data/iris.data",header = None)
print("读取的对象类型：",type(iris_data))
iris_data.columns = ['sepal length', 'sepal width', 
                     'petal length', 'petal width', 'class']
print("DataFrame数据的前3行：\n", iris_data[:3],sep="")

# 打乱行顺序，存入新DataFrame对象中
df = shuffle(iris_data)  
print("随机打乱后，某一次运行的前3行：\n", df[:3],sep="")
# 也可以利用DataFrame.sample()采样的方法来打乱顺序，frac是要返回的比例
#df1 = iris_data.sample(frac=1)  # frac=1表示返回全部，达到打乱顺序的效果
#display(df1[:3])

# 将class列中的字符串替换为数字
df1 = iris_data.replace({"class":{'Iris-setosa': 0, 
                                  'Iris-versicolor': 1, 
                                  'Iris-virginica': 2}})
print("将分类文本标记替换为数字后的前3行：\n", df1[:3],sep="")
# 打乱顺序
#df2 = shuffle(df1)
#display(df2[:3])

# 获取特征数组
X=df1.values[:,:4]
print("前3个样本的特征数组：\n", X[:3],sep="")
# 获取分类标记数组
y = df1.values[:,-1:]
print("前3个样本的分类标记：\n", y[:3],sep="")


# ### 方式3：在线加载数据集

# In[3]:


import numpy as np
import pandas as pd 
# 在线读取数据
iris_data = pd.read_csv('https://archive.ics.uci.edu/'+
        'ml/machine-learning-databases/iris/iris.data', header=None)
iris_data.columns = ['sepal length', 'sepal width', 
                     'petal length', 'petal width', 'class']

# 通过pd.set_option设置DataFrame打印时右对齐
pd.set_option("display.unicode.east_asian_width",True)
print("前两行：\n",iris_data.head(2),sep="")
# np.unique中设置return_inverse=True，
#  返回旧列表iris_data.iloc[:,-1:]中各类别名称在新列表iris_classes中的位置。
#  这样，class_code中获得的就是各样本类别的编码
iris_classes, class_code = np.unique(iris_data.iloc[:,-1:].values, 
                                     return_inverse=True) 
print("类别名称：",iris_classes,sep="")
print("类别标签（编码）：\n",class_code,sep="")
iris_data["class"] = class_code
print("前两行：\n",iris_data.head(2))


# ## 步骤2：划分训练集与测试集

# In[4]:


from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

#这里重新加载数据，可以使用前面已加载的数据
iris_data = load_iris()    #默认返回Bunch类型的对象
#display(iris_data["data"][:3])
#display(iris_data["feature_names"])
#display(iris_data["target"][:3])
#display(iris_data["target_names"])

X_train, X_test, y_train, y_test = train_test_split(
                    iris_data.data, iris_data.target, 
                    test_size=0.2, random_state = 0)

print("训练集特征与标签数据形状：", X_train.shape, y_train.shape)
print("测试集特征与标签数据形状：", X_test.shape, y_test.shape)


# ## 步骤3：训练数据可视化，直观了解训练集数据

# In[5]:


import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris

# 绘制训练数据的散点图矩阵
X_train_df = pd.DataFrame(X_train, columns=iris_data.feature_names)

pd.plotting.scatter_matrix(X_train_df, c=y_train,
                figsize=(10,8), hist_kwds={"bins" : 20},
                alpha = .9, s=80)      # s表示标记点大小

plt.show()


# ## 步骤4：构建模型（这里使用线性支持向量机）

# In[6]:


from sklearn.svm import LinearSVC

# 构建模型对象
class_model = LinearSVC(random_state=1,
                        max_iter=10000)  # max_iter表示最大迭代次数
# 从训练数据集中学习模型参数
class_model.fit(X_train, y_train)   


# ## 步骤5：评估模型

# In[7]:


y_train_predict = class_model.predict(X_train) 
y_test_predict = class_model.predict(X_test)
print("测试集的预测分类：", y_test_predict)
print("测试集的真实分类：", y_test)

# 准确率计算方式1
import numpy as np
print("训练集准确率：", np.mean(y_train_predict==y_train))
print("测试集准确率：", np.mean(y_test_predict==y_test))

# 准确率计算方式2
print("训练集准确率：", class_model.score(X_train,y_train))
print("测试集准确率：", class_model.score(X_test,y_test))

from sklearn import metrics
# 打印混淆矩阵
print("混淆矩阵：\n", 
      metrics.confusion_matrix(y_test,y_test_predict))
# 打印分类性能报告
print("分类性能报告：\n", 
      metrics.classification_report(y_test,y_test_predict))


# ## 步骤6：做出预测
# 
# 假设我们发现了一朵鸢尾花，花萼长4.5cm、宽2.8cm，花瓣长2.5cm、宽0.3cm。利用刚才学习的模型，判断新的鸢尾花属于哪个品种。

# In[8]:


import numpy as np
# 为测量的四个特征数据构建一个二维的numpy数组，因为scikit-learn模型fit()方法输入参数为numpy二维数组类型
X_new = np.array([[4.5, 2.8, 2.5, 0.3]])

# 调用模型的pridect()方法来预测这个新的鸢尾花类型
class_code = class_model.predict(X_new)
print("类型代码：", class_code)
print("类型名称：", iris_data["target_names"][class_code])


# ## 保存已训练的模型，可供下次直接使用

# In[9]:


# 将模型保存到文件
import joblib
joblib.dump(class_model,"./savedmodel/class_model.pkl")


# ## 加载已保存的模型，直接用于新数据的预测

# In[10]:


load_model = joblib.load("./savedmodel/class_model.pkl")
X_new = np.array([[4.5, 2.8, 2.5, 0.3]])
class_code = load_model.predict(X_new)
print("类型代码：", class_code)
print("类型名称：", iris_data["target_names"][class_code])

